

#ifndef __IRR_PLANE_3D_H_INCLUDED__
#define __IRR_PLANE_3D_H_INCLUDED__

#include "irrTypes.h"
#include "irrMath.h"
#include "vector3d.h"

namespace irr
{
    namespace core
    {

        //! Enumeration for intersection relations of 3d objects
        enum EIntersectionRelation3D 
        {
            ISREL3D_FRONT = 0,
            ISREL3D_BACK,
            ISREL3D_PLANAR,
            ISREL3D_SPANNING,
            ISREL3D_CLIPPED
        };

        //! Template plane class with some intersection testing methods.
        template <class T>
        class plane3d
        {
        public:

            // Constructors

            plane3d(): Normal(0,1,0) { recalculateD(vector3d<T>(0,0,0)); };
            plane3d(const vector3d<T>& MPoint, const vector3d<T>& Normal) : Normal(Normal) { recalculateD(MPoint); };
            plane3d(T px, T py, T pz, T nx, T ny, T nz) : Normal(nx, ny, nz) { recalculateD(vector3d<T>(px, py, pz)); };
            plane3d(const plane3d<T>& other) :D(other.D), Normal(other.Normal) {};
            plane3d(const vector3d<T>& point1, const vector3d<T>& point2, const vector3d<T>& point3)	{ setPlane(point1, point2, point3);	};

            // operators

            inline bool operator==(const plane3d<T>& other) const { return (D==other.D && Normal==other.Normal);};
            inline bool operator!=(const plane3d<T>& other) const { return !(D==other.D && Normal==other.Normal);};

            // functions

            void setPlane(const vector3d<T>& point, const vector3d<T>& nvector)
            {
                Normal = nvector;
                Normal.normalize();
                recalculateD(point);
            }

            void setPlane(const vector3d<T>& nvect, T d)
            {
                Normal = nvect;
                D = d;
            }

            void setPlane(const vector3d<T>& point1, const vector3d<T>& point2, const vector3d<T>& point3)
            {
                // creates the plane from 3 memberpoints
                Normal = (point2 - point1).crossProduct(point3 - point1);
                Normal.normalize();

                recalculateD(point1);
            }


            //! Returns an intersection with a 3d line.
            //! \param lineVect: Vector of the line to intersect with.
            //! \param linePoint: Point of the line to intersect with.
            //! \param outIntersection: Place to store the intersection point, if there is one.
            //! \return Returns true if there was an intersection, false if there was not.
            bool getIntersectionWithLine(const vector3d<T>& linePoint, const vector3d<T>& lineVect,
                vector3d<T>& outIntersection) const
            {
                T t2 = Normal.dotProduct(lineVect);

                if (t2 == 0)
                    return false;

                T t =- (Normal.dotProduct(linePoint) + D) / t2;			
                outIntersection = linePoint + (lineVect * t);
                return true;
            }

            //! Returns where on a line between two points an intersection with this plane happened.
            //! Only useful if known that there is an intersection.
            //! \param linePoint1: Point1 of the line to intersect with.
            //! \param linePoint2: Point2 of the line to intersect with.
            //! \return Returns where on a line between two points an intersection with this plane happened.
            //! For example, 0.5 is returned if the intersection happened exectly in the middle of the two points.
            f32 getKnownIntersectionWithLine(const vector3d<T>& linePoint1,
                const vector3d<T>& linePoint2) const
            {
                core::vector3df vect = linePoint2 - linePoint1;
                T t2 = Normal.dotProduct(vect);
                return -((Normal.dotProduct(linePoint1) + D) / t2);
            }

            //! Returns an intersection with a 3d line, limited between two 3d points.
            //! \param linePoint1: Point 1 of the line.
            //! \param linePoint2: Point 2 of the line.
            //! \param outIntersection: Place to store the intersection point, if there is one.
            //! \return Returns true if there was an intersection, false if there was not.
            bool getIntersectionWithLimitedLine(const vector3d<T>& linePoint1, 
                const vector3d<T>& linePoint2, vector3d<T>& outIntersection) const
            {
                return (	getIntersectionWithLine(linePoint1, linePoint2 - linePoint1, outIntersection) &&
                    outIntersection.isBetweenPoints(linePoint1, linePoint2));
            }

            //! Classifies the relation of a point to this plane.
            //! \param point: Point to classify its relation.
            //! \return Returns ISREL3D_FRONT if the point is in front of the plane,
            //! ISREL3D_BACK if the point is behind of the plane, and
            //! ISREL3D_PLANAR if the point is within the plane.
            EIntersectionRelation3D classifyPointRelation(const vector3d<T>& point) const
            {
                f32 d = Normal.dotProduct(point) + D;

                if (d < -ROUNDING_ERROR)
                    return ISREL3D_FRONT;

                if (d > ROUNDING_ERROR)
                    return ISREL3D_BACK;

                return ISREL3D_PLANAR;
            }

            //! Recalculates the distance from origin by applying
            //! a new member point to the plane.
            void recalculateD(const vector3d<T>& MPoint)
            {
                D = - MPoint.dotProduct(Normal);
            }

            //!\return Returns a member point of the plane.
            vector3d<T> getMemberPoint() const
            {
                return Normal * -D;
            }

            //! Tests if there is a intersection between this plane and another
            //! \return Returns true if there is a intersection.
            bool existsInterSection(const plane3d<T>& other) const
            {
                vector3d<T> cross = other.Normal.crossProduct(Normal);
                return cross.getLength() > 1e-08f;
            }

            //! Intersects this plane with another.
            //! \return Returns true if there is a intersection, false if not.
            bool getIntersectionWithPlane(const plane3d<T>& other, vector3d<T>& outLinePoint,
                vector3d<T>& outLineVect) const
            {
                f32 fn00 = Normal.getLength();
                f32 fn01 = Normal.dotProduct(other.Normal);
                f32 fn11 = other.Normal.getLength();
                f32 det = fn00*fn11 - fn01*fn01;

                if (fabs(det) < 1e-08f)
                    return false;

                det = 1.0f / det;
                f32 fc0 = (fn11*-D + fn01*other.D) * det;
                f32 fc1 = (fn00*-other.D + fn01*D) * det;

                outLineVect = Normal.crossProduct(other.Normal);
                outLinePoint = Normal*(T)fc0 + other.Normal*(T)fc1;
                return true;
            }

            //! Returns the intersection point with two other planes if there is one.
            bool getIntersectionWithPlanes(const plane3d<T>& o1, 
                const plane3d<T>& o2, vector3d<T>& outPoint) const
            {
                vector3d<T> linePoint, lineVect;
                if (getIntersectionWithPlane(o1, linePoint, lineVect))
                    return o2.getIntersectionWithLine(linePoint, lineVect, outPoint);

                return false;
            }

            //! Returns if the plane is front of backfacing. Note that this only
            //! works if the normal is Normalized.
            //! \param lookDirection: Look direction.
            //! \return Returns true if the plane is front facing, which mean it would
            //! be visible, and false if it is backfacing.
            bool isFrontFacing(const vector3d<T>& lookDirection) const
            {
                return Normal.dotProduct(lookDirection) <= 0.0f;
            }

            //! Returns the distance to a point.  Note that this only
            //! works if the normal is Normalized.
            T getDistanceTo(const vector3d<T>& point) const
            {
                return point.dotProduct(Normal) + D;
            }

            // member variables

            vector3d<T> Normal;		// normal vector
            T D;					// distance from origin
        };


        //! Typedef for a f32 3d plane.
        typedef plane3d<f32> plane3df;
        //! Typedef for an integer 3d plane.
        typedef plane3d<s32> plane3di;

    } // end namespace core
} // end namespace irr

#endif

